package tests;

import java.util.ArrayList;
import java.util.List;
import java.lang.reflect.Method;


import jeconbond.automata.AutomataUtils;
import jeconbond.automata.INode;
import jeconbond.automata.IOutcome;
import jeconbond.automata.Node;
import jeconbond.automata.Outcome;
import jeconbond.automata.behaviour.ListPlanWalker;
import jeconbond.automata.behaviour.IWalkStrategy;
import jeconbond.economic.agent.AutomataAgent;
import jeconbond.economic.agent.IEconomicAgent;
import jeconbond.economic.market.strategy.MultiplyPerformPriceStrategy;
import jeconbond.economic.market.strategy.ProductNecessityMerchant;
import jeconbond.economic.market.strategy.ObserveCostsProfitMerchantDecorator;
import jeconbond.economic.natural.strategy.NaturalMerchant;
import jeconbond.economic.production.IResourceNecessityInformer;
import jeconbond.economic.production.IResourceNecessityInformer.InfoItem;
import jeconbond.economic.resources.*;
import jeconbond.economic.systems.natural.NaturalResourceRedestributionSystem;
import jeconbond.economic.systems.market.*;
import jeconbond.economic.IEconomicEngine;
import jeconbond.economic.EconomicEngine;
import jeconbond.experiment.market.equilibrium.MooreExperimentSettings;
import jeconbond.experiment.market.equilibrium.generate.MooreAgentsCreator;
import jeconbond.experiment.market.equilibrium.generate.MooreEngineInitialiser;
import jeconbond.experiment.process.IJEBExperimentContext;
import junit.framework.Assert;
import junit.framework.TestCase;
import tests.experiments.antstrat.TestAntBasedStrategies.ResourceProvider;

public class TestAgents extends TestCase {
	public void testEconomicAgent() {
		IResourceType food = new ResourceType("Food");
		IResourceType excrement = new ResourceType("Excrement");
		NaturalResourceRedestributionSystem nrrs = 
			new NaturalResourceRedestributionSystem();
		IEconomicAgent theAgent = 
			new tests.fakes.TamagoochiAgent(food, excrement, nrrs);
		nrrs.storeResource(food, 2.0);
		Assert.assertEquals(0.0, nrrs.howMuchWasStored(excrement));
		theAgent.processPreProduceStage();
		theAgent.processProduceStage();
		theAgent.processPostProduceStage();
		Assert.assertEquals(1.0, nrrs.howMuchWasStored(food));
		Assert.assertEquals(100.0, nrrs.howMuchWasStored(excrement));
	}
	
	public void testAutomataAgent() {
		INode root = new Node("root");
		AutomataAgent aa = new AutomataAgent(root);
		IEconomicAgent agent = aa;
		Assert.assertNotNull(agent);
		Assert.assertEquals("root", aa.getRoot().getName());
	}
	
	public void testPurchasingAgent() {
		tests.fakes.StoreCallsMerchant callsStorrage =
			new tests.fakes.StoreCallsMerchant();
		AutomataAgent agent = new AutomataAgent(null);
		agent.setMerchant(callsStorrage);
		agent.setWalkStrategy(new tests.fakes.SingleStateWaklStrategy());
		Assert.assertEquals(0, callsStorrage.getPurchaseCallsCount());
		Assert.assertEquals(0, callsStorrage.getSellingCallsCount());
		agent.processPreProduceStage();
		Assert.assertEquals(1, callsStorrage.getPurchaseCallsCount());
		Assert.assertEquals(0, callsStorrage.getSellingCallsCount());
		agent.processProduceStage();
		agent.processPostProduceStage();
		Assert.assertEquals(1, callsStorrage.getPurchaseCallsCount());
		Assert.assertEquals(1, callsStorrage.getSellingCallsCount());
	}
	
	public void testAAgWalkStrategy() {
		INode root = new Node("root");
		INode prev = root;
		for (int i = 0; i < 3; i++) {
			prev.getOutcomes().add(new Outcome(new Node("node" + i + "0")));
			prev.getOutcomes().add(new Outcome(new Node("node" + i + "1")));
			prev.getOutcomes().add(new Outcome(new Node("node" + i + "2")));
			prev = prev.getOutcomes().get(1).getDestination();
		}
		
		AutomataAgent aa = new AutomataAgent(root);
		aa.setWalkStrategy(new tests.fakes.Choose1WaklStrategy());
		Assert.assertEquals("root", aa.getState().getName());
		aa.processProduceStage();
		aa.processProduceStage();
		aa.processProduceStage();
		Assert.assertEquals("node21", aa.getState().getName());
	}
	
	public void testListPlanWalkStrategy() {
		INode n1 = new Node("n1");
		INode n2 = new Node("n2");
		INode n3 = new Node("n3");
		INode n2altout = new Node("n2altout");
		
		ResourceStorrage irs = new ResourceStorrage();
		ResourceStorrage ors = new ResourceStorrage();
		
		n1.getOutcomes().add(new Outcome(n2));
		n2.getOutcomes().add(new Outcome(n2altout));
		n2.getOutcomes().add(new Outcome(n3));
		
		List<IOutcome> plan = new ArrayList<IOutcome>();
		plan.add(n1.getOutcomes().get(0));
		plan.add(n2.getOutcomes().get(1));
		IWalkStrategy lps = new ListPlanWalker(plan, irs, ors);
		
		INode state = n1;
		state = lps.doWalk(state).getDestination();
		state = lps.doWalk(state).getDestination();
		Assert.assertEquals("n3", state.getName());
	}
	
	public void testResourceNecessityInformer() {
		IResourceType wood = new ResourceType("Wood");
		IResourceNecessityInformer informer = new tests.fakes.WoodIsNecessary(wood);
		IResourceNecessityInformer.InfoItem item = informer.listInfoItems().get(0);
		Assert.assertEquals("Wood", item.basket.type.getName());
	}
	
	public void testListPlanNecessityInformer() {
		IResourceType wood = new ResourceType("Wood");
		IResourceType oil = new ResourceType("Oil");
		
		INode n1 = new Node("n1");
		INode n2 = new Node("n2");
		INode n3 = new Node("n3");
		INode n4 = new Node("n4");
		
		n1.getOutcomes().add(
				new Outcome(
						n2,
						new ResourceBasket(wood, 1.0)
				)
		);
		n2.getOutcomes().add(
				new Outcome(
						n3,
						new ResourceBasket(wood, 0.5),
						new ResourceBasket(oil, 0.5)
				)
		);
		n3.getOutcomes().add(
				new Outcome(
						n4,
						new ResourceBasket(oil, 1.0)
				)
		);
		
		ResourceStorrage irs = new ResourceStorrage();
		ResourceStorrage ors = new ResourceStorrage();
		
		List<IOutcome> nodesList = new ArrayList<IOutcome>();
		nodesList.add(n1.getOutcomes().get(0));
		nodesList.add(n2.getOutcomes().get(0));
		nodesList.add(n3.getOutcomes().get(0));
		ListPlanWalker lpWalker = 
			new ListPlanWalker(nodesList, irs, ors);
		IResourceNecessityInformer lpInformer = lpWalker;
		
		InfoItem item = null;
		item = lpInformer.listInfoItems().get(0);
		Assert.assertEquals("Wood", item.basket.type.getName());
		Assert.assertEquals(1.0, item.basket.value);
		Assert.assertEquals(0, item.proirityLevel);
		item = lpInformer.listInfoItems().get(1);
		Assert.assertEquals("Wood", item.basket.type.getName());
		Assert.assertEquals(0.5, item.basket.value);
		Assert.assertEquals(1, item.proirityLevel);
		item = lpInformer.listInfoItems().get(2);
		Assert.assertEquals("Oil", item.basket.type.getName());
		Assert.assertEquals(0.5, item.basket.value);
		Assert.assertEquals(1, item.proirityLevel);
		item = lpInformer.listInfoItems().get(3);
		Assert.assertEquals("Oil", item.basket.type.getName());
		Assert.assertEquals(1.0, item.basket.value);
		Assert.assertEquals(2, item.proirityLevel);
		
		INode state = n1;
		irs.storeResource(wood, 1.0);
		state = lpWalker.doWalk(state).getDestination();
		item = lpInformer.listInfoItems().get(0);
		Assert.assertEquals("Wood", item.basket.type.getName());
		Assert.assertEquals(0.5, item.basket.value);
		Assert.assertEquals(0, item.proirityLevel);
		item = lpInformer.listInfoItems().get(1);
		Assert.assertEquals("Oil", item.basket.type.getName());
		Assert.assertEquals(0.5, item.basket.value);
		Assert.assertEquals(0, item.proirityLevel);
		item = lpInformer.listInfoItems().get(2);
		Assert.assertEquals("Oil", item.basket.type.getName());
		Assert.assertEquals(1.0, item.basket.value);
		Assert.assertEquals(1, item.proirityLevel);
	}
	
	public void testNaturalMerchant() {
		NaturalResourceRedestributionSystem nrrs = 
			new NaturalResourceRedestributionSystem();
		IResourceType wood = new ResourceType("Wood");
		
		ResourceStorrage inputRS = new ResourceStorrage();
		ResourceStorrage outputRS = new ResourceStorrage();
		NaturalMerchant naturalMerchant = new NaturalMerchant(
				inputRS, 
				outputRS,
				new tests.fakes.WoodIsNecessary(wood),
				nrrs
		); 
		
		nrrs.storeResource(wood, 1.0);
		naturalMerchant.processPurchase();
		Assert.assertEquals(1.0, inputRS.receiveResource(wood, 1.0));
		outputRS.storeResource(wood, 2.0);
		Assert.assertEquals(0.0, nrrs.howMuchWasStored(wood));
		naturalMerchant.processSelling();
		Assert.assertEquals(2.0, nrrs.howMuchWasStored(wood));
	}
	
	public void testPriorityLevelsAndNaturalMerchant() {
		NaturalResourceRedestributionSystem nrrs = 
			new NaturalResourceRedestributionSystem();
		IResourceType wood = new ResourceType("Wood");
		IResourceType oil = new ResourceType("Oil");
		
		ResourceStorrage inputRS = new ResourceStorrage();
		ResourceStorrage outputRS = new ResourceStorrage();
		tests.fakes.WoodIsNecessary informer =
			new tests.fakes.WoodIsNecessary(wood);
		informer.setOilPriority(oil, 0);
		NaturalMerchant naturalMerchant = new NaturalMerchant(
				inputRS, 
				outputRS,
				informer,
				nrrs
		); 
		
		nrrs.storeResource(wood, 1.0);
		nrrs.storeResource(oil, 1.0);
		naturalMerchant.processPurchase();
		Assert.assertEquals(1.0, inputRS.receiveResource(oil, 1.0));
		
		nrrs.storeResource(oil, 1.0);
		informer.setOilPriority(oil, 1);
		naturalMerchant.processPurchase();
		Assert.assertEquals(0.0, inputRS.receiveResource(oil, 1.0));
	}

	public void testProdNecMerchant4onlySingleGoal() {
		IResourceType[] resources = ResourceUtils.generateResources("r", 8);
		INode root = tests.experiments.antstrat.TestAntBasedStrategies.genSmallExample(resources);
		AutomataAgent agent = new AutomataAgent(root);
		List<IOutcome> plan = new ArrayList<IOutcome>();
		addPlan1(plan, root);
		ListPlanWalker informer = new ListPlanWalker(
				plan,
				agent.getInputRS(),
				agent.getOutputRS(),
				0,
				plan.size(),
				true
		);
		IMoneyAccount money = new MoneyAccount(1e5);
		IMarketResourceRedestributionSystem mrrs =
				new DefaultMarketResourceRedestributionSystem();

		ProductNecessityMerchant pnMmerchant = new ProductNecessityMerchant(
				mrrs,
				money,
				agent.getInputRS(),
				agent.getOutputRS(),
				informer,
				1
		);
		ObserveCostsProfitMerchantDecorator observeCPMerch = new ObserveCostsProfitMerchantDecorator(
				pnMmerchant,
				money,
				10,
				agent.getOutputRS()
		);
		double agentGreedPercent = 1.1;
		pnMmerchant.setPerformPriceStrategy(
				new MultiplyPerformPriceStrategy(observeCPMerch, agentGreedPercent)
		);
		agent.setMerchant(observeCPMerch);
		agent.setWalkStrategy(informer);

		ResourceProvider resourceProvider =
				new ResourceProvider(mrrs, resources);

		IEconomicEngine engine = new EconomicEngine();
		engine.addAgents(
				agent,
				resourceProvider,
				observeCPMerch
		);

		resourceProvider.makeResourcesCheap(
				100.0,
				resources[0],
				resources[1]
		);
		engine.processSteps(100, mrrs);
		while (agent.getState() != root) {
			engine.processSteps(1, mrrs);
		}
		ISellProposition[] props = mrrs.getSellPropositionsList(resources[2]);
		ResourceUtils.sortSellPropositions(props);
		ISellProposition prop = props[props.length - 1];
		/**
		 * Expired: ProdNecMerchant func was extended
		 */
		//Assert.assertTrue(prop.getPrice() > 45.0);
		Assert.assertNotNull(prop);

		addPlan2(plan, root);
		informer.setPlan(plan);
		engine.processSteps(5, mrrs);
		props = mrrs.getSellPropositionsList(resources[6]);
		ResourceUtils.sortSellPropositions(props);
		prop = props[props.length - 1];
		Assert.assertEquals(2, props.length);
		for (ISellProposition p : props) {
			Assert.assertTrue(p.getPrice() < 1.1);
		}

		engine.processSteps(100, mrrs);
		props = mrrs.getSellPropositionsList(resources[6]);
		Assert.assertEquals(2, props.length);
		ResourceUtils.sortSellPropositions(props);
		prop = props[props.length - 1];
		for (ISellProposition p : props) {
			Assert.assertTrue(p.getPrice() < 1.1);
		}
	}
	
	public void testObserveCostsProfitMerchantDecorator_WorkWithMultiProdAtOneOutcome() {
		MooreExperimentSettings settings = new MooreExperimentSettings();
		settings.agentsCount = 1;
		settings.observeCostsTraceLength = 10;
		settings.initMoney = 1e5;
		settings.agentGreedPercent = 1.0;
		settings.resources = ResourceUtils.generateResources("r", 9);
		settings.rrsFactory = new DefaultMarketRRSFactory();

		INode root = new Node("root");
		INode n = new Node("n");
		IOutcome out = new Outcome(n, new ResourceBasket(settings.resources[0], 10.0));
		out.setProducedResources(
				new ResourceBasket(settings.resources[1], 1.0)
				, new ResourceBasket(settings.resources[2], 1.0)
		);
		root.getOutcomes().add(out);
		out = new Outcome(root, new ResourceBasket(settings.resources[3], 10.0));
		out.setProducedResources(
				new ResourceBasket(settings.resources[4], 1.0)
				, new ResourceBasket(settings.resources[5], 3.0)
		);
		n.getOutcomes().add(out);

		MooreAgentsCreator eeCr = new MooreAgentsCreator();
		IJEBExperimentContext experimentContext = (IJEBExperimentContext)
				settings.getExperimentContextFactory().newContext(settings);
		AutomataAgent[] agents = eeCr.automata2agents(new INode[]{root}, experimentContext);
		MooreEngineInitialiser eeeInit = new MooreEngineInitialiser();

		IEconomicEngine engine = new EconomicEngine();
		eeeInit.initialise(engine, agents, experimentContext);
		IMarketResourceRedestributionSystem mrrs =
				(IMarketResourceRedestributionSystem) experimentContext.getRRS();

		ResourceProvider resourceProvider = new ResourceProvider(mrrs, settings.resources);
		engine.addAgents(resourceProvider);

		engine.processSteps(100, mrrs);

		agents[0].processPostProduceStage();
		Assert.assertEquals(5.0, getBigProp(mrrs, settings.resources[1]).getPrice());
		Assert.assertEquals(5.0, getBigProp(mrrs, settings.resources[2]).getPrice());
		Assert.assertEquals(2.5, getBigProp(mrrs, settings.resources[4]).getPrice());
		Assert.assertEquals(2.5, getBigProp(mrrs, settings.resources[5]).getPrice());
	}

	public void testStoreCostsProdPurchaser_InvalidatesLastItemInTrace() {
		ObserveCostsProfitMerchantDecorator purchaser = new ObserveCostsProfitMerchantDecorator(
				null,
				null,
				10,
				null
		);
		IResourceType wood = new ResourceType("Wood");
		for (int i = 0; i < 10; i++) {
			writeCost(purchaser, 10.0);
			purchaser.writeProduced(1.0, wood);
		}
		Assert.assertEquals(10.0, purchaser.performPrice(wood));
		writeCost(purchaser, 10.0);
		writeCost(purchaser, 10.0);
		Assert.assertEquals(10.0, purchaser.performPrice(wood));
		purchaser.writeProduced(1.0, wood);
		Assert.assertEquals(11.0, purchaser.performPrice(wood));
	}

	private ISellProposition getBigProp(IMarketResourceRedestributionSystem mrrs, IResourceType resource) {
		ISellProposition[] props = mrrs.getSellPropositionsList(resource);
		ResourceUtils.sortSellPropositions(props);
		return props[props.length - 1];
	}

	private void writeCost(ObserveCostsProfitMerchantDecorator decorator, double val) {
		try {
			Class<?> clazz = decorator.getClass();
			Method method = clazz.getDeclaredMethod("writeCost", double.class);
			method.setAccessible(true);
			method.invoke(decorator, val);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private void addPlan2(List<IOutcome> plan, INode root) {
		plan.clear();
		plan.add(root.getOutcomes().get(1));
		INode n2 = root.getOutcomes().get(1).getDestination();
		AutomataUtils.easyTracePlan(root, n2, plan);
	}

	private void addPlan1(List<IOutcome> plan, INode root) {
		plan.clear();
		AutomataUtils.easyTracePlan(root, plan);
	}
}
